/**
 * @description: require
 * @date: 2021/1/13
 */

const path = require('path');
const fs = require('fs');
const vm = require('vm');

function Module(id) {
    this.id = id;
    this.filename = id;
    this.loaded = false;
    this.parent = null;
    this.children = [];
    this.exports = {};
}

const wrapper = [
    '(function (exports, require, module, __filename, __dirname) { ',
    '\n});'
];
Module.wrap = function(script) {
    return wrapper[0] + script + wrapper[1];
};
Module._cache = {};
Module._pathCache = {};
Module._extensions = {};

Module.prototype.load = function (filename) {
    if (this.loaded) {
        return;
    }

    let extension = path.extname(filename);
    Module._extensions[extension](this, filename);
    this.loaded = true;
}

Module.prototype._compile = function (content, filename) {
    const module = this;
    const thisValue = module.exports;
    let require = makeRequireFunction(module);
    const dirname = path.dirname(filename);
    const codeFn = wrapSafe(filename, content);

    let result;

    try {
        codeFn.call(thisValue, module.exports, require, module, filename, dirname);
    } catch (e) {
        delete Module._cache[filename];
        throw e;
    }

    return result;
}

Module.prototype.require = function (id) {
    return Module._load(id);
}

Module._resolveFilename = function (request) {
    let firsSymbol = request[0];
    let p = '';

    // 解析规则
    if (firsSymbol === '.' || firsSymbol === '/') {
        p = path.resolve(request);
    } else {
        p = path.resolve('node_modules', request);
    }

    // 路径存在文件则直接返回
    try {
        fs.accessSync(p);
        return p;
    } catch (e) {
        // empty
    }

    // 没有加后缀
    if (!/\..+$/.test(p)) {
        let exts = Object.keys(Module._extensions);

        let findExt = exts.find(ext => {
            let fullPath = `${p}${ext}`;
            try {
                fs.accessSync(fullPath);
                return true;
            } catch (e) {
                return false;
            }
        })

        if (findExt) {
            return `${p}${findExt}`;
        }
    }

    // 啥到没找到
    let error = `Cannot find module "${request}"`;
    throw new Error(error);
}

Module._extensions['.js'] = function(module, filename) {
    if (!filename.endsWith('.js')) {
        return;
    }
    const content = fs.readFileSync(filename, 'utf8');
    module._compile(content, filename);
};

Module._extensions['.json'] = function(module, filename) {
    const content = fs.readFileSync(filename, 'utf8');

    try {
        module.exports = JSON.parse(content);
    } catch (err) {
        err.message = filename + ': ' + err.message;
        throw err;
    }
};

Module._extensions['.node'] = function () {}

// 0. Module._load
// 1. 检查 Module._cache，是否缓存之中有指定模块
// 2. 如果缓存之中没有，就创建一个新的Module实例
// 3. 将它保存到缓存
// 4. 使用 module.load() 加载指定的模块文件，读取文件内容
// 5. 调用 module.compile()，传入(exports, require, module)等参数，在沙箱中执行代码
// 5. 如果加载/解析过程报错，就从缓存删除该模块
// 6. 返回该模块的 module.exports
Module._load = function (id) {
    let fullPath = Module._resolveFilename(id);
    let module = Module._cache[fullPath];

    if (module) {
        return module.exports;
    }

    module = new Module(fullPath);
    Module._cache[fullPath] = module;
    module.load(fullPath);
    return module.exports;
}

function wrapSafe(filename, content) {
    const wrapper = Module.wrap(content);
    return vm.runInThisContext(wrapper, {
        filename,
        lineOffset: 0,
        displayErrors: true
    });
}

function makeRequireFunction(mod) {
    let require = function (id) {
        return mod.require(id);
    };

    require.resolve = function (request) {
        return Module._resolveFilename(request);
    }

    require.cache = Module._cache;
    require.main = process.mainModule;
    require.extensions = Module._extensions;
    return require;
}

function req(id) {
    return Module._load(id);
}

let i = req('./index');
console.log(i);
